/**
 * AGNLinearParameterHandle.java
 */
package agngui.agnmodel;
import javax.media.j3d.PickRay;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * This class is for handles that can stretch along a distinct, arbitrary
 * axis.
 */
public class AGNLinearParameterHandle extends AGNParameterHandle {
    /**
     * AGNLinearParameterHandle
     * 
     * Initializes this linear parameter object.
     *
     * @param object The object that this handle belongs to
     * @param point  The point (in world space) that the handle currently lies on
     * @param axis   The linear axis (in world space) this parameter can travel on
     * @param identifier The parameter identifier (depends on the AGNObject this handle belongs to)
     */
    public AGNLinearParameterHandle( AGNObject object, Point3d point, Vector3d axis, String identifier ) {
        super( AGNParameterHandle.TYPE_LINEAR, object, point, axis, identifier );
        recentValue = new Vector3d(point).length(); 
    }

    /**
     * calculateNewValueFromWorldLocation
     *
     * Calculates the new handle value based on the world point passed, and
     * stores the most recent value internally.
     *
     * @param worldPoint The point in world space projected by
     *                   projectClickOntoControlAxis
     * @return The calculated value (depends on handle type)
     */
    public double calculateNewValueFromWorldLocation( Point3d worldPoint ) {
        TransformGroup objectTransformGroup = object.getTransformGroup();
        Transform3D    transform = new Transform3D();
        Vector3d       delta     = new Vector3d( worldPoint );

        objectTransformGroup.getTransform( transform );
		transform.setScale(AGNObject.ONE_DISTANCE_UNIT);

        transform.transform( delta );
        recentValue = delta.length();
        
        return recentValue;
    }

    /**
     * projectClickOntoControlAxis
     *
     * Projects a mouse click onto the actual control axis in world space and   
     * gets the world-space intersection point.
     *
     * @param  mX The mouse click x-coordinate
     * @param  mY The mouse click y-coordinate
     * @param  renderer The model renderer, needed for camera access and projection
     * @return The point in world-space of the finally projected point.
     */
    public Point3d projectClickOntoControlAxis( int mX, int mY, AGNModelRenderer renderer ) {
        // The plane in this function refers to the plane whose normal is the cross product
        // of the control axis and the camera's up vector, located at this.point
		PickRay  ray;
        Vector3d rayDir        = new Vector3d();
        Vector3d controlAxis   = new Vector3d( axis );
        Vector3d planeNormal   = new Vector3d();
        Vector3d planeToPoint  = new Vector3d();
        Vector3d up            = new Vector3d( renderer.getCamera().getUp() );
        Point3d  clickPoint    = new Point3d();
		Point3d  rayOrigin     = new Point3d();
        double   D;
        double   t;
        double   Vd;
     
        planeNormal.cross( controlAxis, up );
        // Check to see if controlAxis and up are parallel or antiparallel
        if( planeNormal.length() < 1e-10 ) {
            // Plane normal is facing us directly
            planeNormal.set( renderer.getCamera().getLookAtVector() );
            planeNormal.negate();
        }

        controlAxis.normalize();
        planeNormal.normalize();

        // Get the point of the mouse click in world space (via ray-plane intersection)
		ray = renderer.getClickRay( mX, mY );
		ray.get(rayOrigin, rayDir);
	    Vd = planeNormal.dot(rayDir);

		if(Vd == 0) {
			throw new RuntimeException("Divide by Zero in Ray-Plane Intersection (linear handle)");
		}

	    D = -planeNormal.dot(new Vector3d(point));
		t = -(planeNormal.dot(new Vector3d(rayOrigin)) + D)/Vd;
		
		if(t < 0) {
			//plane is behind the camera?  That shouldn't be possible.
		}

		rayDir.scale( t );
		clickPoint.add( rayOrigin, rayDir );

        // Project the point on the plane onto the control axis
        planeToPoint.sub( clickPoint, point );
        controlAxis.scale( planeToPoint.dot( controlAxis ) );

        clickPoint.set( controlAxis );
		clickPoint.add(point);
		
		return new Point3d( clickPoint );
    }
}
